主从复制的问题
- 当master节点发生故障时,需要手动进行故障转移
- 写能力与存储能力受限,写能力和存储能力都依赖于master节点
Redis Sentinel架构
在主从复制的基础上,新增多个Redis Sentinel节点,这些Sentinel不存储任何的数据。这些Sentinel节点会完成Redis的故障判断并故障转移的处理,然后通知客户端。一套Redis Sentinel集群可以监控多套Redis主从,每一套Redis主从通过master-name作为标识。
客户端不直接连接Redis服务,而连接Redis Sentinel。在Redis Sentinel中清楚哪个节点是master节点。
故障转移流程
- 多个Sentinel发现并确认master有问题
- 选举出一个Sentinel作为领导
- 选出一个slave作为master
- 通知其余slave成为新的master的slave
- 通知客户端主从发生的变化
- 等待老的master复活成为新master的slave
Redis Sentinel的相关配置
配置 | 含义 |
---|---|
port ${port} | sentinel的端口号 |
dir “/redisDataPath” | redis的工作目录 |
logfile “${port}.log” | redis的日志文件 |
sentinel monitor mymaster 127.0.0.1 7000 2 | 名称为mymaster的主从 masterIP=127.0.0.1 masterPort=7000 2个sentinel发现这个master有问题后执行故障转移 |
sentinel down-after-milliseconds mymaster 30000 | 每个sentinel在连续ping 30000ms不通后认为有问题 |
sentinel parallel-syncs mymaster 1 | 在故障转移时,该名称为mymaster的集群中 同一时间点只允许1个节点进行复制 |
sentinel failover-timeout mymaster 180000 | 故障转移的超时时间 |
Redis Sentinel的安装与配置
1.配置开启主从节点
- redis-7000.conf
1 | port 7000 |
- redis-7001.conf
1 | port 7001 |
- redis-7002.conf
1 | port 7002 |
2.配置开启sentinel监控主节点(sentinel是特殊的redis)
- redis-26379.conf(redis sentinel的默认端口是26379)
1 | port 26379 |
- redis-26380.conf
1 | port 26380 |
- redis-26381.conf
1 | port 26381 |
客户端接入基本原理
- 客户端需要所有的sentinel节点以及对应的masterName
- 客户端会遍历所有的sentinel节点,获取一个可用的sentinel节点
- 向可用的sentinel调用 sentinel get-master-addr-by-name masterName, 可用的sentinel将返回master节点信息。
- 客户端连接该master节点,调用role或者role replication,确认该节点是master节点。
- 如果master发生故障转移,sentinel是能够感知,并通过发布订阅模型将最新的master信息告知客户端
1 | Set<String> sentinelSet = new HashSet<String>() {{ |
三个定时任务
- 每10秒每个sentinel对master和slave执行info
- 发现slave节点
- 确认主从关系
- 每2秒每个sentinel通过mster节点的channel交换信息(pub/sub)
- 通过sentinel:hello频道交互
- 交互对节点的“看法”和自身信息
- 每1秒每个sentinel对其他sentinel和redis执行ping
主观下线与客观下线
- 主观下线:每个sentinel节点对Redis节点失败的看法。
- sentinel down-after-milliseconds masterName timeout
- 每个sentinel节点每秒会对Redis节点进行ping,当连续timeout毫秒之后还没有得到PONG,则sentinel认为redis下线。
- 客观下线:所有sentinel节点对Redis节点失败达成共识。
- sentinel monitor masterName ip port quorum
- 大于等于quorum个sentinel主观认为Redis节点失败下线
- 通过sentinel is-master-down-by-addr提出自己认为Redis master下线
领导者选举
- 原因:只有sentinel节点完成故障转移
- 选举:通过 sentinel is-master-down-by-addr 命令都希望成为领导者
- 每个主观下线的sentinel节点向其他sentinel节点发送命令,要求将它设置为领导者
- 收到命令的Sentinel节点如果没有同意其他Sentinel节点发送的命令,那么将同意该请求,否则拒绝。
- 如果该Sentinel节点发现自己的票数已经超过Sentinel集合半数且超过quorum,那么将它成为领导者
- 如果此过程有多个Sentinel节点成为了领导者,那么将等待一段时间重新进行选举
故障转移(Sentinel领导者节点完成之后)
- 从slave节点中选出一个“合适的”节点作为新的master节点
- 选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,不存在则继续
- 选择复制偏移量最大的slave节点(复制的最完整性),如果存在则返回,不存在则继续
- 选择runId最小的slave节点
- 对上面的slave节点执行slave no one命令让其成为master节点
- 向其余的slave节点发送命令,让它们成为新master节点的slave节点,复制规则和parallel-syncs参数有关。
- 更新对原来master节点配置为slave,并保持对其“关注”,当其恢复后命令它去复制新的master节点
节点运维(上线与下线)
生产节点下线可能原因
- 机器下线:过保等情况
- 机器性能不足:例如CPU、内存、磁盘、网络等
1.主节点
1 | ##节点下线 |
2.从节点
需要区分是临时下线还是永久下线。例如需要做一些配置、AOF、RDB等方面的清理工作。
当上线时候,执行slaveof masterIp masterPort即可
3.Sentinel节点
需要区分是临时下线还是永久下线。例如需要做一些配置的清理工作。
高可用读写分离
可以考虑尝试扩展 JedisSentinelPool,维护master节点与slave节点池
并关注三条消息来维护节点池
- switch-master : 切换主节点(从节点晋升为主节点)
- convert-to-slave : 切换从节点(原主节点降为从节点)
- sdown : 主动下线
redis系列:哨兵
1 简介
Sentinel(哨兵)是Redis 的高可用性解决方案:通过哨兵可以创建一个当主服务器出现故障时自动将从服务器升级为主服务器的一个分布式系统。解决了主从复制出现故障时需要人为干预的问题。
这篇介绍哨兵的搭建,以及哨兵是如何进行哨兵发现和主从切换等功能。
2 准备工作
在原先主从的基础上,每台机器启动一个哨兵。架构图如下
2.1 配置
配置文件如下
1 | daemonize yes |
2.2 启动方式
有两种方式
1 | src/redis-sentinel sentinel.conf |
3 开始搭建
哨兵搭建的过程如下
哨兵集群搭建完毕后,日志内容如下
启动后配置文件sentinel.conf会增加内容
1 | daemonize yes |
4 启动流程
接下来看看哨兵集群启动过程中,Redis内部发生了什么。步骤如下
- 初始化服务器
- 使用Sentinel专用代码
- 初始化Sentinel状态
- 创建连向主服务器的网络连接
4.1 初始化服务器
Sentinel 本质上只是一个运行在特殊模式下的Redis服务器,所以初始化时和不同的Redis服务器初始化没什么较大的区别。有区别的就是哨兵服务器并不会载入RDB文件和AOF文件,还有一些命令功能哨兵服务器不使用。
4.2 使用Sentinel专用代码
初始化服务器之后,哨兵服务器会将一部分普通Redis的服务器使用的代码替换成哨兵专用的代码。以下就是哨兵的命令列表,代码文件在github.com/antirez/red…
1 | struct redisCommand sentinelcmds[] = { |
4.3 初始化Sentinel状态
在应用了哨兵专用的代码之后,哨兵会初始化状态,这个哨兵状态结构包含了服务器中所有和哨兵功能有关的状态。结构体代码位置在也在entinel.c文件中,结构体代码如下
1 | /* Main state. */ |
启动哨兵出现的日志如下
1 | # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo |
哨兵id如下
1 | # Sentinel ID is b457cbbcda1991f540d56c6e8faea123a668b16c |
4.4 创建连向主服务器的网络连接
初始化哨兵的最后一步是创建连向被监视的主服务器的网络连接,哨兵将会成为主服务器的客户端。哨兵会向主服务器创建两个异步网络连接
- 命令连接,用于向主服务器发送命令,并接受命令。
- 订阅连接,专门用于订阅主服务器的sentinel:hello频道。
启动哨兵过程到这里就结束了,接下来将进入下个环节。
5 获取信息
获取信息阶段会获取主服务器信息和从服务器信息以及哨兵的相关信息。
5.1 获取主服务器信息
哨兵默认会以10s一次的频率,发送命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。
监控主服务器
1 | # +monitor master learnSentinelMaster 192.168.17.101 6379 quorum 2 |
通过分析主服务器返回的信息,可以获取到两方面的信息
- 主服务器本身的信息
- 从服务器的信息
获取到从服务器信息之后,哨兵会更新保存主服务器实例结构的slaves字典。
5.2 获取从服务器信息
当哨兵发现主服务器有新的从服务器出现时,哨兵会为这个新的从服务器创建相应的实例结构之外,还会创建到从服务器的命令连接和订阅连接。
发现新的从服务器会出现如下日志
1 | * +slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379 |
在创建命令连接之后,会发送INFO命令获取信息。通过从服务器回复的信息中,可以获得以下内容
- 从服务器的运行ID run_id
- 从服务器的角色 role
- 从服务器的IP地址 master_host,以及主服务器的端口号master_port
- 从服务器的连接状态 matser_link_status
- 从服务器的优先级 salve_pripority
- 从服务器的复制偏移量 slave_repl_offest
获取到这些信息之后,会对之前创建的从服务器实例结构进行更新。
5.3 获取其他Sentinel的信息
在获取其他哨兵的信息之前,先要知道向主服务器和从服务器发送信息
和接收来自主服务器和从服务器的频道信息
。
5.3.1 向主服务器和从服务器发送信息
发送的命令格式如下
1 | PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>" |
PUBLISH是发布消息的命令,__sentinel__:hello
是频道的名称,后面就是一些参数,参数信息如下
5.3.2 接收来自主服务器和从服务器的频道信息
当Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送命令:
1 | SUBSCRIBE __sentinel__:hello |
表示哨兵订阅__sentinel__:hello
这个频道,接收这个频道的消息。
其他哨兵可以通过接收这个频道的消息来发现其他哨兵的存在。
5.3.3 发现哨兵
通过接收__sentinel__:hello
频道的消息可以发现其他哨兵的存在。当哨兵接收到一条来自__sentinel__:hello
频道的消息时,会出现下方
- 判断该消息是否是自己发送的,是则忽略这条消息
- 消息不是自己发送时,说明有新的哨兵
- 查看自己是否存有该哨兵的信息,有则更新该哨兵的信息
- 没有则创建一个新的哨兵实例结构,并保存到sentinels字典中
注:sentinels字典是专门保存哨兵信息的
5.3.4 创建连向其他哨兵的命令连接
当Sentinel通过频道信息发现一个新的Sentinel时,不仅会在自身的sentinels字典中为新Sentinel创建实例结构,还会创建一个连向新Sentinel的命令连接,同时新的Sentinel也会创建一个连向这个Sentinel的命令连接,最终多个Sentinel将形成一个互相连接的网络。
注:哨兵之间不会创建订阅连接
发现哨兵的日志如下
1 | * +sentinel sentinel f0230d4fdf1ffc7865852de71f16b3017cc1617c 192.168.17.101 26379 @ learnSentinelMaster 192.168.17.101 6379 |
6 模拟101主服务器掉钱
模拟101主服务器掉钱的过程如下
断线重连的日志内容如下
接下来开始分析断线过程中的每一步骤
- 检测主观下线状态
- 检测客观下线状态
- 选举领头哨兵
- 故障转移
6.1 检测主观下线状态
在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(主,从,其他Sentinel)发送 PING命令 ,通过判断返回的内容来判断是否在线,命令分为有效回复和无效回复两种。
- 有效回复
- +PING
- -LOADING
- -MASTERDOWN
- 无效回复
- 除有效回复以外的内容
- 指定时间内没有回复
配置文件中的down-after-milliseconds参数可以设置指定时间,在这个时间段内没有收到回复则判定该服务器处于主观下线状态。
1 | sentinel down-after-milliseconds learnSentinelMaster 5000 |
现在101客户端上输入以下命令,让服务器睡眠30秒
1 | debug sleep 30 |
此时查看哨兵日志,等待5秒后出现以下内容
1 | # +sdown master learnSentinelMaster 192.168.17.101 6379 |
6.2 检测客观下线状态
当一个哨兵将一个主服务器判断为主观下线之后,会向其他监视该主服务器的哨兵进行询问,当有足够数量的哨兵判定主服务器下线时,会执行故障转移操作 。
注:这里不对哨兵之间互相发送的消息进行说明
在配置中可以决定判定主服务器进入客观下线状态所需要的服务器数量,下方配置的最后一个参数就是所需的哨兵数量,这里填写的是2
1 | sentinel monitor learnSentinelMaster 192.168.17.101 6379 2 |
下面的日志说明了主服务器101已经进入客观下线状态
1 | # +odown master learnSentinelMaster 192.168.17.101 6379 #quorum 2/2 |
当前纪元被更新 ,试图故障恢复
1 | # +new-epoch 2 |
此时开始准备选举领头哨兵进行故障转移
6.3 选举领头哨兵
当主服务器被判定为客观下线之后,各个哨兵服务器将会选举出一个领头哨兵,有这个领头哨兵对下线服务器进行故障转移操作,选举领头哨兵的规则如下:
- 所有在线的Sentinel都有被选为领头Sentinel的资格;
- 每次进行选举之后,不论选举是否成功,所有Sentinel的配置纪元都会自增一次;
- 在一个配置纪元里,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里就不会再更改;
- 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel;
- 当一个Sentinel向另一个Sentinel发送请求命令,并且命令中的runid不是*而是运行id时,这表示源Sentinel要求目标Sentinel将前者设置为后者的局部领头Sentinel。
- 设置局部领头Sentinel的原则是先到先得,之后所有的设置要求都会被拒绝;
- 目标Sentinel在收到命令后,会返回一条回复,回复中的leader_runid参数和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元;
- 源Sentinel在收到回复后,会检查配置纪元与自己是否相等,如果相同,且leader_runid与自己相同,那么表示自己成为了目标的局部领头;
- 如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么它成为领头Sentinel;
- 因为领头的产生需要半数哨兵的支持,并且每个哨兵在每个配置纪元只能设置一次局部领头Sentinel,所以在一个配置纪元里面,只会出现一个领头Sentinel;
- 如果在给定时限内没有选出领头Sentinel,那么各个Sentinel将在一段时间之后再次进行选举,直到选出来。
下方就是选举领头哨兵的日志内容
1 | # +vote-for-leader b457cbbcda1991f540d56c6e8faea123a668b16c 2 |
6.4 故障转移
在选举出领头哨兵之后,领头哨兵需要执行故障转移操作,操作主要分为三个步骤
- 选出新的主服务器
- 修改从服务器的复制目标
- 将旧的主服务器变为从服务器
6.4.1 选出新的主服务器
此时,领头哨兵需要选出新的主服务器,然后向新的主服务器发送SLAVEOF no one命令,将这个从服务器转换为主服务器。
选择过程会过滤掉不符合要求的服务器:
- 处于下线或者断线状态的从服务器
- 最近5秒内没有回复过领头哨兵的INFO信息的从服务器
- 与已下线主服务器连接断开超过(down-after-milliseconds * 10)毫秒的从服务器。(与主服务器客观下线时间进行比较)
新的主服务器只选择通过上面的测试,并在上面的标准基础上排序:
- Slave通过Redis实例的redis.conf文件配置的slave-priority排序。优先级越低越被优先考虑。
- 如果优先级相同,检查slave的复制偏移量,并选择接收更多数据的slave。
- 如果多个slave有相同的优先级和同样的处理数据过程,就会执行一个更进一步的验证,选择一个有较短run ID的slave。run ID 对于 slave没太大用,但是非常有助于选择slave的过程,而不是随机选择slave。
选出合适的从节点作为新的主节点
1 | 2101:X 31 Jul 19:13:35.709 # +failover-state-select-slave master learnSentinelMaster 192.168.17.101 6379 |
开始讲102转换为主节点
1 | * +failover-state-send-slaveof-noone slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379 |
6.4.2 修改从服务器的复制目标
当新的主服务器出现之后,领头哨兵会向其他从服务器发送slaveof 命令去复制新的主服务器。
下方记录了领头哨兵向从服务器发送 SALVEOF命令去复制新的主服务器。
1 | * +slave-reconf-sent slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379 |
6.4.3 将旧的主服务器变为从服务器
这时候如果下线的主服务器重启上线了怎么办?这也是故障转移要做的最后一步,将已下线的主服务器设置为新的主服务器的从服务器。当下线的主服务器重新上线时,哨兵就会向它发送SLAVEOF命令,让他成为新的主服务器的从服务器。
此时101服务器上线
1 | # -odown master learnSentinelMaster 192.168.17.101 6379 |
故障转移成功完成。所有slaves被重新配置为新master的从
1 | # +failover-end master learnSentinelMaster 192.168.17.101 6379 |
转换101状态
1 | * +slave slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379 |
这时候再次查看配置文件会发现多了一行sentinel current-epoch 2
1 | #后台启动 |
7 相关配置
1 | # Example sentinel.conf |